package com.clei;

import com.clei.utils.JarUtil;
import com.clei.utils.PrintUtil;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 应用入口
 *
 * @author Y
 * @date 2024-03-04
 **/
public class TestApplication {

    /**
     * className -> fullClassName map
     * lowerCassClassName -> fullClassNameList
     */
    private final static Map<String, List<String>> CLASS_GROUP = new HashMap<>();

    /**
     * class文件后缀
     */
    private final static String CLASS_SUFFIX = ".class";

    /**
     * class文件后缀长度
     */
    private final static int CLASS_SUFFIX_LENGTH = CLASS_SUFFIX.length();

    // CLASS_MAP初始化
    static {
        scanClass();
        // fullClassName 排序
        CLASS_GROUP.forEach((k, v) -> Collections.sort(v));
    }

    public static void main(String[] args) {
        if (null == args || 0 == args.length) {
            PrintUtil.log("未指定类名");
            return;
        }
        PrintUtil.log("args : {} ", Arrays.toString(args));
        // 类名获取
        String className = args[0];
        boolean fullName = className.contains(".");
        String key = fullName ? className.substring(className.lastIndexOf('.') + 1).toLowerCase() : className.toLowerCase();
        List<String> nameList = CLASS_GROUP.get(key);
        PrintUtil.log("className : {}, fullClassName : {}", className, nameList);
        if (null == nameList) {
            PrintUtil.log("未知类名 className : {}", className);
            return;
        }
        // 是否有多个
        String fullClassName;
        if (nameList.size() > 1) {
            // 有多个时 找到第一个大小写全匹配的
            Optional<String> first = nameList.stream().filter(className::equals).findFirst();
            if (!first.isPresent()) {
                PrintUtil.log("未知类名 className : {}", className);
                return;
            } else {
                fullClassName = first.get();
            }
        } else {
            fullClassName = nameList.get(0);
        }
        PrintUtil.log("className : {}, fullClassName : {}", className, fullClassName);
        // 参数获取
        Object param = null;
        if (args.length > 1) {
            int paramLength = args.length - 1;
            param = new String[paramLength];
            System.arraycopy(args, 1, param, 0, paramLength);
        }
        // 调用
        try {
            // 加载
            Class<?> clazz = Class.forName(fullClassName);
            // 执行main方法
            Method mainMethod = clazz.getMethod("main", String[].class);
            mainMethod.invoke(null, param);
        } catch (Exception e) {
            PrintUtil.log("执行出错 className : {}", className, e);
        }
    }

    /**
     * scanClass
     */
    private static void scanClass() {
        // basePackage
        Class<TestApplication> currentClass = TestApplication.class;
        String currentClassName = currentClass.getName();
        String basePackage = currentClassName.substring(0, currentClassName.lastIndexOf('.'));
        String packagePath = basePackage.replaceAll("\\.", "/");
        URL resourceUrl = ClassLoader.getSystemResource(packagePath);
        // 根据协议处理
        String protocol = resourceUrl.getProtocol();
        switch (protocol) {
            case "jar":
                scanJarPackageClass(resourceUrl, basePackage);
                break;
            case "file":
                scanFilePackageClass(new File(resourceUrl.getPath()), basePackage, true);
                break;
            default:
        }
    }

    /**
     * 扫描包下的类 file
     *
     * @param file        file
     * @param packageName 包名
     * @param base        基础包 true/false
     */
    private static void scanFilePackageClass(File file, String packageName, boolean base) {
        if (null == file || !file.exists()) {
            return;
        }
        String fileName = file.getName();
        if (file.isFile()) {
            if (fileName.endsWith(CLASS_SUFFIX)) {
                String className = fileName.substring(0, fileName.length() - CLASS_SUFFIX_LENGTH);
                String fullClassName = packageName + '.' + className;
                putInClassGroup(className.toLowerCase(), fullClassName);
            }
        } else {
            File[] files = file.listFiles();
            if (null != files) {
                if (!base) {
                    packageName = packageName + '.' + fileName;
                }
                for (File f : files) {
                    scanFilePackageClass(f, packageName, false);
                }
            }
        }
    }

    /**
     * 扫描包下的类 jar
     *
     * @param url         jarUrl
     * @param packageName 包名
     */
    private static void scanJarPackageClass(URL url, String packageName) {
        String packagePath = packageName.replaceAll("\\.", "/");
        List<String> resources = JarUtil.getResources(url);
        Map<String, List<String>> classGroup = resources.stream()
                // class && package.***.class && 非内部class
                .filter(r -> r.endsWith(CLASS_SUFFIX) && r.startsWith(packagePath) && !r.contains("$"))
                .map(r -> {
                    String fullClassName = r.replaceAll("/", ".");
                    return fullClassName.substring(0, fullClassName.length() - CLASS_SUFFIX_LENGTH);
                })
                .collect(Collectors.groupingBy(n -> n.substring(n.lastIndexOf('.') + 1).toLowerCase()));
        CLASS_GROUP.putAll(classGroup);
    }

    /**
     * putInClassGroup
     *
     * @param className     className
     * @param fullClassName fullClassName
     */
    private static void putInClassGroup(String className, String fullClassName) {
        List<String> list = CLASS_GROUP.get(className);
        if (null == list) {
            list = new ArrayList<>(1);
            list.add(fullClassName);
            CLASS_GROUP.put(className, list);
        } else {
            PrintUtil.log("类名重复 className : {}, oldValue : {}, newValue : {}", className, list, fullClassName);
            list.add(fullClassName);
        }
    }
}
